﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AgilePlannerDT.Data;

using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using Microsoft.TeamFoundation.Server;
using AgilePlannerDT.Data.Impl;
using System.Text.RegularExpressions;
using Microsoft.TeamFoundation.WorkItemTracking.Proxy;
using Microsoft.TeamFoundation.Framework.Client;

namespace AgilePlannerDT.ThirdPartySync
{
    public class TFSSyncronizer : ISyncronizer, IDisposable
    {
        
        TfsTeamProjectCollection projectCollection;
        AgilePlannerDT.Data.Project apProject;

        public TFSSyncronizer(Data.Project project, TfsTeamProjectCollection projectCollection)
        {
            projectCollection.Authenticate();
            this.projectCollection = projectCollection;
            this.apProject = project;
            TeamFoundationIdentity identity;
            projectCollection.GetAuthenticatedIdentity(out identity);
            Console.WriteLine("Authenticated as:" + identity.DisplayName);
        }

        #region WorkItems

        private WorkItemStore getStore() {
            var workItemStore = projectCollection.GetService<WorkItemStore>();
            if (workItemStore.Projects.Count <= 0)
            {
                throw new ApplicationException("There are no projects in this server");
            }
            return workItemStore;
        }

        private WorkItemTypeCollection getWorkItemTypes(WorkItemStore store){
            var workItemTypes = store.Projects[apProject.Name].WorkItemTypes;
            if (workItemTypes.Count <= 0)
            {
                throw new ApplicationException("There are no work item types in this project");
            }
            return workItemTypes;
        }

        private void checkValidity(WorkItem item){
            StringBuilder builder = new StringBuilder();
            foreach(Field f in item.Fields.Cast<Field>()){
                if(!f.IsValid){
                    builder.Append("Field: " + f.Name + " with Value: " + f.Value.ToString() + " is invalid.\n");
                }
            }
            if (builder.Length > 0) throw new ApplicationException(builder.ToString());
        }

        public void createWorkItem(Data.StoryCard storyCard)
        {
            var workItemStore = getStore();
            var workItemTypes = getWorkItemTypes(workItemStore);
            // create a new work item
            var workItem = new WorkItem(workItemTypes[workItemTypeOf(storyCard)]);
            convert(storyCard, workItem);

            checkValidity(workItem);

            workItem.Save();
            storyCard.SyncID = workItem.Id;
        }

        public void deleteWorkItem(Data.StoryCard storyCard)
        {
            // get a reference to the work item tracking service
            var workItemStore = getStore();

            var workItem = workItemStore.GetWorkItem((int)storyCard.SyncID);
            workItem.State = "Closed";
            workItem.Save();
        }

        private void convert(Data.StoryCard storyCard, WorkItem workItem)
        {
            workItem.Title = storyCard.Name;

            workItem.Fields["Story Points"].Value = storyCard.MostlikelyEstimate;
            if (apProject.Backlog.ID != storyCard.Parent)
            {
                //assign to iteration
                workItem.IterationPath = apProject.Name + "\\Iteration_" + formatDate(((Iteration)storyCard.ParentCard).StartDate);
            }

            if(storyCard.Status.Equals("Accepted")){
                workItem.Fields["State"].Value = "Closed";
            } else if(storyCard.Status.Equals("Completed")){
                workItem.Fields["State"].Value = "Resolved";
            } else {
                workItem.Fields["State"].Value = "Active";
            }

            workItem.Description = storyCard.Description;
            if(!storyCard.CardOwner.Equals("Owner"))workItem.Fields["Assigned To"].Value = storyCard.CardOwner;

            //workItem.Fields["WorstCaseEstimate"].Value = storyCard.WorstCaseEstimate;
            //workItem.Fields["BestCaseEstimate"].Value = storyCard.BestCaseEstimate;
            //workItem.Fields["ActualEffort"].Value = storyCard.ActualEffort;
            //workItem.Fields["FitID"].Value = Int32.Parse(storyCard.FitID);
            //workItem.Fields["RallyID"].Value = Int32.Parse(storyCard.RallyID);
        }

        private string workItemTypeOf(Data.StoryCard storyCard)
        {
            //if (storyCard.Background.Equals(CardConstants.RED))
            //{
            //    return "Bug";
            //}
            return "User Story";
        }

        public void updateWorkItem(Data.StoryCard storyCard)
        {
            // get a reference to the work item tracking service
            var workItemStore = getStore();
            var workItem = workItemStore.GetWorkItem((int)storyCard.SyncID);
            // edit the work item
            workItem.Open();
            convert(storyCard, workItem);

            // make sure all the fields are valid before saving
            checkValidity(workItem);

            // save the edited work item
            workItem.Save();
        }

        #endregion

        #region Get Stuff

        private List<StoryCard> getStoryCardsFrom(WorkItemCollection workItems)
        {
            List<StoryCard> cards = new List<StoryCard>();
            foreach (WorkItem item in workItems)
            {
                StoryCard card = new StoryCardDataObject();
                convert(item, card);
                cards.Add(card);
            }
            return cards;
        }

        public List<StoryCard> getActiveWorkItems()
        {
            WorkItemStore storage = getStore();
            WorkItemCollection workItemCollection =
                storage.Query("SELECT * FROM workitems WHERE [System.State] != \"Closed\" AND [System.WorkItemType] = \"User Story\" AND [System.TeamProject] = \"" + apProject.Name + "\" ");
            List<StoryCard> cards = getStoryCardsFrom(workItemCollection);
            return cards;
        }

        public static String workItemUsablePath(String iterAreaPath)
        {
            String ret;
            Match m = Regex.Match(iterAreaPath, "\\\\(.*?)\\\\Iteration(\\\\.*)");
            ret = m.Groups[1].Value + m.Groups[2].Value;
            return ret;
        }

        public List<Iteration> getActiveIterations()
        {
            WorkItemStore storage = getStore();
            WorkItemCollection timeBoxCollection =
                storage.Query("SELECT * FROM workitems WHERE [System.State] != \"Closed\" AND [System.WorkItemType] = \"IterationDetails\" AND [Team Project] = \""+apProject.Name+"\"");
            List<Iteration> iterations = new List<Iteration>();
            foreach (WorkItem timeBox in timeBoxCollection)
            {
                Iteration iteration = new IterationDataObject();
                convert(timeBox, iteration);
                iterations.Add(iteration);
                
                WorkItemCollection workItemCollection =
                    storage.Query("SELECT * FROM workitems WHERE [System.WorkItemType] = \"User Story\" AND ([System.IterationPath] = \"" + timeBox.IterationPath + "\") ");
                List<StoryCard> cards = getStoryCardsFrom(workItemCollection);
                foreach (StoryCard card in cards)
                {
                    iteration.AddStoryCard(card);
                }
            }

            return iterations;
        }

        public bool contains(Data.IndexCard card)
        {
            if (card.SyncID <= 0) return false;
            var workItemStore = getStore();
            WorkItem item = workItemStore.GetWorkItem((int)card.SyncID);
            return item != null;
        }

        #endregion

        #region StoryCards

        private void convert(WorkItem workItem, StoryCard card)
        {
            card.Name = workItem.Title;

            card.MostlikelyEstimate = extractInt(workItem.Fields["Story Points"]);
            card.Parent = 0; //TODO
            if (workItem.State.Equals("Closed"))
            {
                card.Status = "Accepted";
            }
            else if (workItem.State.Equals("Resolved"))
            {
                card.Status = "Completed";
            }
            else
            {
                card.Status = "Defined";
            }

            card.Description = workItem.Description;
            card.CardOwner = (String)workItem.Fields["Assigned To"].Value;
            card.SyncID = workItem.Id;

            //card.WorstCaseEstimate = extractInt(workItem.Fields["WorstCaseEstimate"]);
            //card.BestCaseEstimate  = extractInt(workItem.Fields["BestCaseEstimate"]);
            //card.ActualEffort      = extractInt(workItem.Fields["ActualEffort"]);
            //card.FitID             = extractString(workItem.Fields["FitID"], "0");
            //card.RallyID           = extractString(workItem.Fields["RallyID"], "0");
        }

        private int extractInt(Field f)
        {
            if (f.Value == null) f.Value = 0;
            return Int32.Parse(f.Value.ToString());
        }
        
        private string extractString(Field f, string dFault)
        {
            if (f.Value == null) f.Value = dFault;
            return f.Value.ToString();
        }

        public void updateStoryCard(Data.StoryCard storyCard)
        {
            var workItemStore = getStore();
            WorkItem timeBox = workItemStore.GetWorkItem((int)storyCard.SyncID);
            convert(timeBox, storyCard);
        }

        #endregion

        #region Iteration

        private void convert(WorkItem timebox, Iteration iteration)
        {
            if (timebox.Fields["Start Date"].Value == null)
                timebox.Fields["Start Date"].Value = new DateTime();
            if (timebox.Fields["Finish Date"].Value == null)
                timebox.Fields["Finish Date"].Value = new DateTime();
            iteration.StartDate = (DateTime)timebox.Fields["Start Date"].Value;
            iteration.EndDate = (DateTime)timebox.Fields["Finish Date"].Value;
            iteration.Status = (String)timebox.State;
            iteration.SyncID = timebox.Id;
            iteration.Parent = apProject.ID; // parent is always the active project
        }

        public void updateIteration(Data.Iteration iteration)
        {
            var workItemStore = getStore();
            WorkItem timeBox = workItemStore.GetWorkItem((int)iteration.SyncID);
            convert(timeBox, iteration);
        }

        #endregion

        #region TimeBox

        public static string formatDate(DateTime d)
        {
            StringBuilder strbuilder = new StringBuilder();
            strbuilder.Append(d.Year).Append("-");
            if (d.Month < 10) strbuilder.Append("0");
            strbuilder.Append(d.Month).Append("-");
            if (d.Day < 10) strbuilder.Append("0");
            strbuilder.Append(d.Day);
            return strbuilder.ToString();
        }

        private void clearCache()
        {
            ICommonStructureService commonStructureService = projectCollection.GetService<ICommonStructureService>();
            commonStructureService.ClearProjectInfoCache();
            WorkItemServer server = projectCollection.GetService<WorkItemServer>();
            server.SyncExternalStructures(WorkItemServer.NewRequestId(), commonStructureService.GetProjectFromName(apProject.Name).Uri);
            var workItemStore = getStore();
            workItemStore.RefreshCache();
        }

        public void createTimeBox(Data.Iteration iteration)
        {
            ICommonStructureService commonStructureService = projectCollection.GetService<ICommonStructureService>();
            NodeInfo iterationRootNode = commonStructureService.GetNodeFromPath("\\" + iteration.Project.Name + "\\Iteration");
            
            string iterURI;
            try
            {
                iterURI = commonStructureService.CreateNode("Iteration_" + formatDate(iteration.StartDate), iterationRootNode.Uri);
            }
            catch (Exception e)
            {
                iterURI = commonStructureService.GetNodeFromPath("\\" + iteration.Project.Name + "\\Iteration\\Iteration_" + formatDate(iteration.StartDate)).Uri;
            }
            commonStructureService.ClearProjectInfoCache();
            NodeInfo iterationNode = commonStructureService.GetNode(iterURI);

            clearCache();
            // get a reference to the list of work item types defined for the first team project
            var workItemStore = getStore();
            var workItemTypes = getWorkItemTypes(workItemStore);

            // create a new work item
            WorkItem workItem;
            try
            {
                workItem = new WorkItem(workItemTypes["IterationDetails"]);
            }catch(Exception e){
                throw new Exception("WorkItem Type Iteration Details is missing.",e);
            }
            convert(iteration, workItem);
            checkValidity(workItem);
            
            workItem.Save();
            iteration.SyncID = workItem.Id;
            //iteration.Name = iterationNode.Path;
        }

        public void convert(Iteration iteration, WorkItem workItem)
        {
            workItem.Fields["Iteration Path"].Value = 
                ""+iteration.Project.Name+"\\Iteration_"+formatDate(iteration.StartDate);
            workItem.Fields["Start Date"].Value = iteration.StartDate;
            workItem.Fields["Finish Date"].Value = iteration.EndDate;
            if (iteration.Status.Equals("Accepted"))
            {
                workItem.Fields["State"].Value = "Closed";
            }
            else if (iteration.Status.Equals("Completed"))
            {
                workItem.Fields["State"].Value = "Resolved";
            }
            else
            {
                workItem.Fields["State"].Value = "Active";
            }
        }

        public void updateTimeBox(Data.Iteration iteration)
        {
            clearCache();
            var workItemStore = getStore();
            WorkItem timeBox = workItemStore.GetWorkItem((int)iteration.SyncID);
            
            ICommonStructureService commonStructureService = projectCollection.GetService<ICommonStructureService>();
            NodeInfo iterationNode = commonStructureService.GetNodeFromPath("\\"+iteration.Project.Name+"\\Iteration\\Iteration_"+formatDate((DateTime)timeBox.Fields["Start Date"].Value));
            commonStructureService.RenameNode(iterationNode.Uri, "Iteration_" + formatDate(iteration.StartDate));

            WorkItemServer server = projectCollection.GetService<WorkItemServer>();
            server.SyncExternalStructures(WorkItemServer.NewRequestId(), commonStructureService.GetProjectFromName(iteration.Project.Name).Uri);
            workItemStore.RefreshCache();

            convert(iteration, timeBox);
            timeBox.Save();
            
        }

        public void deleteTimeBox(Data.Iteration iteration)
        {
            clearCache();
            var workItemStore = getStore();
            WorkItem timeBox = workItemStore.GetWorkItem((int)iteration.SyncID);
            timeBox.State = "Closed";
            timeBox.Save();
        }

        #endregion

        #region IDisposable
        public void Dispose()
        {
            projectCollection.Dispose();
        }
        #endregion

    }
}
